<?php

declare(strict_types=1);

namespace App\Middleware;

use App\Master\Enum\RedisKeyEnum;
use App\Model\Arts\UserModel;
use App\Service\SystemService;
use App\Utils\AppResult;
use App\Utils\Control\ActionUtil;
use App\Utils\Control\AuthUser;
use App\Utils\Encrypt\Token;
use App\Utils\LogUtil;
use App\Utils\RedisUtil;
use Hyperf\Coroutine\Coroutine;
use Hyperf\HttpServer\Contract\RequestInterface;
use Hyperf\HttpServer\Contract\ResponseInterface as HttpResponse;
use Hyperf\HttpServer\Router\Dispatched;
use Psr\Container\ContainerInterface;
use Psr\Http\Message\ResponseInterface;
use Psr\Http\Message\ServerRequestInterface;
use Psr\Http\Server\MiddlewareInterface;
use Psr\Http\Server\RequestHandlerInterface;

class ApiAgent implements MiddlewareInterface
{
    // 日志模块名称
    const LOG_MODULE = 'ApiAgent-Middleware-Log';
    const LOG_ACTION = 'verifyToken';
    const PROJECT    = 'Api';

    /**
     * @var ContainerInterface
     */
    protected $container;

    /**
     * @var RequestInterface
     */
    protected $request;

    /**
     * @var HttpResponse
     */
    protected $response;

    protected $action;

    public function __construct(ContainerInterface $container, HttpResponse $response, RequestInterface $request)
    {
        $this->container = $container;
        $this->response  = $response;
        $this->request   = $request;
    }

    public function process(ServerRequestInterface $request, RequestHandlerInterface $handler): ResponseInterface
    {
        //日志统一写入
        LogUtil::getInstance(self::PROJECT . "/");//设置日志存入通道

        Coroutine::defer(function () {
            LogUtil::close();//协程结束后统一写入
        });

        $this->action = ActionUtil::getInstance()->actions($request, self::PROJECT);
        // 记录用户请求参数
        LogUtil::info('请求参数', $this->action['controller'], $this->action['action'], $this->request->all());

        //接口限流，写到中间件中
        if (!RedisUtil::getInstance(RedisKeyEnum::API_REQUEST_TRAFFIC)->requestLimit("{$this->action['controller']}/{$this->action['action']}", 1, 10)) {
            return AppResult::response206('当前访问人数过多，请稍后再试！');
        }

        $token = $this->request->header('Access-Token');
        if (!empty($token) && !in_array("{$this->action['controller']}/{$this->action['action']}", self::tokenWhiteList())) {

            // 校验token
            if (!$user_id = $this->checkToken($token)) {
                return AppResult::response400('登录失效，请重新登录');
            }

            // 查询并记录用户信息
            $user = (new UserModel())->authUserInfo((int)$user_id);
            if (!$user) {
                //防止在缓存后操作得用户登录重试，清除缓存
                $system = new SystemService();
                $system->flushCache((int)$user_id);

                LogUtil::warning('账号不存在', self::LOG_MODULE, self::LOG_ACTION, ['user_id', $user_id]);
                return AppResult::response400('登录失效，请重新登录');
            }

            LogUtil::info('用户ID', $this->action['controller'], $this->action['action'], $user_id);
            AuthUser::getInstance()->set(json_decode(json_encode($user), true));
        }

        return $handler->handle($request);
    }

    /**
     * 校验token
     *
     * @param string $token
     * @return false
     */
    public function checkToken(string $token)
    {
        $action     = ActionUtil::getInstance()->get();
        LogUtil::info('token验证开始', $action['controller'], $action['action'], $token);

        //设置一个假token 用于测试
        if (strpos($token, 'testuid_') !== false) {
            return intval(substr($token, 8));
        }

        $checkToken = Token::getInstance()->check($token);
        if (!$checkToken) {
            LogUtil::warning('token 验证失败', $action['controller'], $action['action'], $checkToken);
            return false;
        }

        //签发时间大于当前服务器时间验证失败
        if (!isset($checkToken['iat']) || $checkToken['iat'] > time()) {
            LogUtil::warning('token 未生效', $action['controller'], $action['action'], $checkToken);
            return false;
        }

        //过期时间小于当前服务器时间验证失败
        if (!isset($checkToken['exp']) || $checkToken['exp'] < time()) {
            LogUtil::warning('token 已失效', $action['controller'], $action['action'], $checkToken);
            return false;
        }

        //验证参数信息
        if (empty($checkToken['user_id'])) {
            LogUtil::warning('token 参数不全', $action['controller'], $action['action'], $checkToken);
            return false;
        }

        return $checkToken['user_id'];
    }

    /**
     * token校验黑名单
     * @return string[]
     */
    public function tokenWhiteList(): array
    {
        return [
            'v1/Common/PassportController/login',
            'v1/Common/PassportController/wxLogin',
        ];
    }
}